﻿/* --------------------------------------------------------------
 * exMath (C) Laurens Ruijtenberg
 * 
 * This code is being distributed under the MS-PL license. Feel
 * free to contact me if you like this code.
 * -------------------------------------------------------------- */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using exMath.Common;
using System.Reflection;

namespace exMath.Calculus
{
    public class Derivative
    {
        private MathFunction _function;
        private Derivative _parentDerivative;
        private string _methodName;
        private Integral _integral;

        /// <summary>
        /// The original function to generate the derivative from.
        /// </summary>
        public MathFunction Function
        {
            get
            {
                return this._function;
            }
        }

        /// <summary>
        /// Returns x function that can calculate the derivative for the mathematical function of this instance.
        /// </summary>
        public virtual MathFunction DerivativeFunction
        {
            get
            {
                switch(this._methodName)
                {
                    case TrigFunctionNames.INTEGRAL:
                        return _integral.Function;
                    case TrigFunctionNames.SIN:
                        return new MathFunction(Math.Cos);
                    case TrigFunctionNames.COS:
                        return new MathFunction(delegate(double x) { return -Math.Sin(x); });
                    case TrigFunctionNames.TAN:
                        return new MathFunction(delegate(double x) { return BasicOperations.Sec2(x); });
                    case TrigFunctionNames.COT:
                        return new MathFunction(delegate(double x) { return -BasicOperations.Csc2(x); });
                    case TrigFunctionNames.SEC:
                        return new MathFunction(delegate(double x) { return BasicOperations.Sec(x) * Math.Tan(x); });
                    case TrigFunctionNames.CSC:
                        return new MathFunction(delegate(double x) { return -(BasicOperations.Csc(x) * BasicOperations.Cot(x)); });
                    default:
                        return new MathFunction(_derivativeFunction);

                }
            }
        }

        public virtual MathFunction AntiDerivativeFunction
        {
            get
            {
                if (_function != null)
                    return _function;

                //check if the original function is a build-in trigonometry function. If this is the case
                //we can simply return it's inverse function instead of calculating the derivative.
                switch (this._methodName)
                {
                    case TrigFunctionNames.SIN:
                        return new MathFunction(delegate(double x) { return -Math.Cos(x); });
                    case TrigFunctionNames.COS:
                        return new MathFunction(Math.Sin);
                    case TrigFunctionNames.SEC2:
                        return new MathFunction(Math.Tan);
                    default:
                        return null;
                }
            }
        }

        /// <summary>
        /// Implements the sum rule.
        /// </summary>
        /// <param name="d1"></param>
        /// <param name="d2"></param>
        /// <returns></returns>
        public static Derivative operator +(Derivative d1, Derivative d2)
        {
            
            return new Derivative(delegate(double x)
            {
                return d1.Function(x) + d2.Function(x);
            });
            
        }

        /// <summary>
        /// Implements the constant multiple rule.
        /// </summary>
        /// <param name="d1"></param>
        /// <param name="Constant"></param>
        /// <returns></returns>
        public static Derivative operator *(Derivative d1, double Constant)
        {
            return new Derivative(delegate(double x)
            {
                return d1.Function(x) * Constant;
            });
        }

        /// <summary>
        /// Implements the difference rule.
        /// </summary>
        /// <param name="d1"></param>
        /// <param name="d2"></param>
        /// <returns></returns>
        public static Derivative operator -(Derivative d1, Derivative d2)
        {
            return new Derivative(delegate(double x)
                {
                    return d1.Function(x) - d2.Function(x);
                });
        }

        /// <summary>
        /// Generates a derivative from a parent derivative.
        /// </summary>
        /// <param name="Parentderivative">derivative to generate anti-derivative from.</param>
        public Derivative(Derivative Parentderivative)
        {
            this._parentDerivative = Parentderivative;
            this._function = _parentDerivative.DerivativeFunction;
            this._methodName = Function.Method.Name;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Function"></param>
        /// <param name="PrecisionInterval"></param>
        public Derivative(MathFunction Function)
        {
            this._function = Function;
            this._methodName = Function.Method.Name;
        }


        public Derivative(Integral dIntegral)
        {
            this._methodName = TrigFunctionNames.INTEGRAL;
            this._integral = dIntegral;
        }


        /// <summary>
        /// Returns the derivative of function f. (f(x + h) - f(x)) / h
        /// In other words dy/IntervalCount[f(x)]
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected virtual double _derivativeFunction(double x)
        {
            double h = (float)(Math.Sqrt(2.2e-16) * x);
            double dy = _function(x + h) - _function(x - h);
            double returnvalue = dy / (h * 2.0);
            return returnvalue;
        }
        
        /// <summary>
        /// Creates x new derivative.
        /// </summary>
        /// <param name="Function"></param>
        /// <param name="PrecisionInterval"></param>
        /// <returns></returns>
        public static Derivative GetDerivative(MathFunction Function)
        {
            return new Derivative(Function);
        }
    }
}
